/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.mosaic;

import java.awt.*;
import java.awt.image.RenderedImage;
import org.eclipse.imagen.*;
import org.eclipse.imagen.media.range.Range;
import org.eclipse.imagen.registry.RenderedRegistryMode;

/**
 * This class is very similar to the Mosaic operation because it returns a composition of various images of the same
 * type (same bands and same dataType). This mosaic implementation has two main differences from the first:
 *
 * <ul>
 *   <li>It doesn't support the threshold weight type.
 *   <li>It handles source no data values.
 * </ul>
 *
 * This new behavior can be summarized with this code:
 *
 * <ul>
 *   <li>Overlay mode
 *       <pre>
 * // s[i][x][y] = pixel value for the source i
 * // d[x][y] = pixel value of the destination
 * d[x][y] = destinationNoData;
 * for (int i=0; i< sources.length(); i++) {
 *      if (!SourceNoDataRange[i].contains(s[i][x][y]) {
 *              d[x][y] = s[i][x][y];
 *              break;
 *      }
 * }
 * </pre>
 *   <li>Blend mode. The destination pixel is calculated as a combination of all the source pixel in the same position.
 *       <pre>
 * // s[i][x][y] = pixel value for the source i
 * // w[i][x][y] = weigthed value of the destination
 * w[i][x][y] = 0;
 * for (int i=0; i< sources.length(); i++) {
 *      if (!SourceNoDataRange[i].contains(s[i][x][y]) {
 *              w[i][x][y] = 1;
 *      }
 * }
 *
 * </pre>
 * </ul>
 *
 * <p>The operation parameters are:
 *
 * <ul>
 *   <li>A Java Bean used for storing image data, ROI and alpha channel if present, and no data Range.
 *   <li>The type of operation executed(Overlay or Blend) .
 *   <li>The destination no data value used if all the pixel source in the same location are no data.
 * </ul>
 *
 * <p>The no data support is provided using the <code>Range</code> class in the JAI-EXT package.
 *
 * <p>In this Mosaic implementation the no data support has been added for geospatial images mosaic elaborations. In
 * that images the there could be different type of nodata and a simple thresholding operation couldn't be enough for
 * avoiding image artifacts.
 *
 * <p>The ROI and alpha mosaic type are equal to those of the classic MosaicOp.
 *
 * @see MosaicOpImage
 */
public class MosaicDescriptor extends OperationDescriptorImpl {

    public static final MosaicType MOSAIC_TYPE_BLEND = new MosaicType("MOSAIC_TYPE_BLEND", 1);

    /** Destination pixel equals first opaque source pixel. */
    public static final MosaicType MOSAIC_TYPE_OVERLAY = new MosaicType("MOSAIC_TYPE_OVERLAY", 0);

    /** serialVersionUID */
    private static final long serialVersionUID = 2718297230579888333L;

    /**
     * The resource strings that indicates the global name, local name, vendor, a simple operation description, the
     * documentation URL, the version number and a simple description of the operation parameters.
     */
    private static final String[][] resources = {
        {"GlobalName", "Mosaic"},
        {"LocalName", "Mosaic"},
        {"Vendor", "org.eclipse.imagen.media"},
        {"Description", "A different mosaic operation which supports noData and doesn't supports threshold"},
        {"DocURL", "wiki github non already available"},
        {"Version", "1.0"},
        {"arg0Desc", "Mosaic Type"},
        {"arg1Desc", "The source Alpha bands"},
        {"arg2Desc", "The source ROIs"},
        {"arg3Desc", "Thresholds used for the mosaic"},
        {"arg4Desc", "Background values"},
        {"arg5Desc", "No data  Values"}
    };

    /** The parameter class. Used for the constructor. */
    private static final Class[] paramClasses = {
        MosaicType.class,
        org.eclipse.imagen.PlanarImage[].class,
        org.eclipse.imagen.ROI[].class,
        double[][].class,
        double[].class,
        org.eclipse.imagen.media.range.Range[].class,
    };

    /** The parameter name list. Used for the constructor. */
    private static final String[] paramNames = {
        "mosaicType", "sourceAlpha", "sourceROI", "sourceThreshold", "backgroundValues", "nodata"
    };

    /** The parameter values. Used for the constructor. */
    private static final Object[] paramDefaults = {
        MosaicDescriptor.MOSAIC_TYPE_OVERLAY, null, null, new double[][] {{1.0}}, new double[] {0.0}, null
    };

    /** Constructor. */
    public MosaicDescriptor() {
        super(
                resources,
                new String[] {RenderedRegistryMode.MODE_NAME},
                0,
                paramNames,
                paramClasses,
                paramDefaults,
                null);
    }

    /** Check if the Renderable mode is supported */
    public boolean isRenderableSupported() {
        return false;
    }

    /**
     * RenderedOp creation method that takes all the parameters, passes them to the ParameterBlockJAI and then call the
     * JAI create method for the mosaic operation with no data support.
     *
     * @param sources The RenderdImage source array used for the operation.
     * @param mosaicType This field sets which type of mosaic operation must be executed.
     * @param sourceAlpha source alpha bands
     * @param sourceROI source ROI
     * @param sourceThreshold source thresholds
     * @param backgroundValues This value fills the image pixels that contain no data.
     * @param nodata array of NoData {@link Range} used for checking nodata values
     * @param renderingHints This value sets the rendering hints for the operation.
     * @return A RenderedOp that performs the mosaic operation with no data support.
     */
    public static RenderedOp create(
            RenderedImage[] sources,
            MosaicType mosaicType,
            PlanarImage[] sourceAlpha,
            ROI[] sourceROI,
            double[][] sourceThreshold,
            double[] backgroundValues,
            Range[] nodata,
            RenderingHints renderingHints) {
        ParameterBlockJAI pb = new ParameterBlockJAI("Mosaic", RenderedRegistryMode.MODE_NAME);

        // All the source images are added to the parameter block.
        int numSources = sources.length;
        for (int i = 0; i < numSources; i++) {
            pb.addSource(sources[i]);
        }
        // Then the parameters are passed to the parameterblockJAI.
        pb.setParameter("mosaicType", mosaicType);
        pb.setParameter("sourceAlpha", sourceAlpha);
        pb.setParameter("sourceROI", sourceROI);
        pb.setParameter("sourceThreshold", sourceThreshold);
        pb.setParameter("backgroundValues", backgroundValues);
        pb.setParameter("nodata", nodata);
        // JAI operation performed.
        return JAI.create("Mosaic", pb, renderingHints);
    }
}
